/*!	 duck.cpp
**	 Template File
**
**	Copyright (c) 2002-2005 Robert B. Quattlebaum Jr., Adrian Bentley
**	Copyright (c) 2007, 2008 Chris Moore
**	Copyright (c) 2009 Nikita Kitaev
**  Copyright (c) 2011 Carlos López
**
**	This package is free software; you can redistribute it and/or
**	modify it under the terms of the GNU General Public License as
**	published by the Free Software Foundation; either version 2 of
**	the License, or (at your option) any later version.
**
**	This package is distributed in the hope that it will be useful,
**	but WITHOUT ANY WARRANTY; without even the implied warranty of
**	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
**	General Public License for more details.
**
*/

#ifdef USING_PCH
#	include "pch.h"
#else
#ifdef HAVE_CONFIG_H
#	include <config.h>
#endif

#include <synfig/general.h>

#include "duck.h"
#include <ETL/misc>

#include <synfig/valuenodes/valuenode_bline.h>
#include <synfig/valuenodes/valuenode_wplist.h>
#include <synfig/valuenodes/valuenode_blinecalctangent.h>
#include <synfig/valuenodes/valuenode_blinecalcvertex.h>
#include <synfig/valuenodes/valuenode_blinecalcwidth.h>
#include <synfig/valuenodes/valuenode_composite.h>

#include <gui/localization.h>
#endif

using namespace std;
using namespace etl;
using namespace synfig;
using namespace studio;

int studio::Duck::duck_count(0);

struct _DuckCounter {
    static int counter;
    ~_DuckCounter()
    {
        if (counter) {
            synfig::error("%d ducks not yet deleted!", counter);
        }
    }
} _duck_counter;

int _DuckCounter::counter(0);

Duck::Duck():
    guid_(0),
    type_(TYPE_NONE),
    editable_(false),
    alternative_editable_(false),
    edit_immediatelly_(false),
    radius_(false),
    tangent_(false),
    hover_(false),
    ignore_(false),
    exponential_(false),
    track_axes_(false),
    lock_aspect_(false),
    scalar_(1),
    origin_(0, 0),
    axis_x_angle_(Angle::deg(0)),
    axis_x_mag_(1),
    axis_y_angle_(Angle::deg(90)),
    axis_y_mag_(1),
    rotations_(synfig::Angle::deg(0)),
    aspect_point_(1, 1)
{
    duck_count++;
    _DuckCounter::counter++;
}

Duck::Duck(const synfig::Point &point):
    guid_(0),
    type_(TYPE_POSITION),
    editable_(false),
    alternative_editable_(false),
    edit_immediatelly_(false),
    radius_(false),
    tangent_(false),
    hover_(false),
    ignore_(false),
    exponential_(false),
    track_axes_(false),
    lock_aspect_(false),
    scalar_(1),
    origin_(0, 0),
    axis_x_angle_(Angle::deg(0)),
    axis_x_mag_(1),
    axis_y_angle_(Angle::deg(90)),
    axis_y_mag_(1),
    point_(point),
    rotations_(synfig::Angle::deg(0)),
    aspect_point_(1, 1)
{
    duck_count++;
    _DuckCounter::counter++;
}

Duck::Duck(const synfig::Point &point, const synfig::Point &origin):
    guid_(0),
    type_(TYPE_NONE),
    editable_(false),
    alternative_editable_(false),
    edit_immediatelly_(false),
    radius_(true),
    tangent_(false),
    hover_(false),
    ignore_(false),
    exponential_(false),
    track_axes_(false),
    lock_aspect_(false),
    scalar_(1),
    origin_(origin),
    axis_x_angle_(Angle::deg(0)),
    axis_x_mag_(1),
    axis_y_angle_(Angle::deg(90)),
    axis_y_mag_(1),
    point_(point),
    rotations_(synfig::Angle::deg(0)),
    aspect_point_(1, 1)
{
    duck_count++;
    _DuckCounter::counter++;
}

Duck::~Duck()
{
    duck_count--;
    _DuckCounter::counter--;
}

synfig::GUID
Duck::get_data_guid()const
{
    synfig::GUID type_guid = synfig::GUID::hasher(get_type());

    if (value_desc_.is_value_node()) {
        return type_guid ^ value_desc_.get_value_node()->get_guid();
    }

    return type_guid ^ synfig::GUID::hasher(get_name());
}

void
Duck::set_name(const synfig::String &x)
{
    name = x;

    if (guid_ == synfig::GUID::zero()) {
        guid_ = synfig::GUID::hasher(name);
    }
}

bool
Duck::operator==(const Duck &rhs)const
{
    if (this == &rhs) {
        return true;
    }

    return
        name == rhs.name &&
        scalar_ == rhs.scalar_ &&
        type_ == rhs.type_ &&
        transform_stack_.size() == rhs.transform_stack_.size();
    // (origin_duck?*origin_duck==*rhs.origin_duck:origin==rhs.origin) &&
    // (shared_point?*shared_point==*rhs.shared_point:point==rhs.point) ;
}

// Sets the location of the duck with respect to the origin
void
Duck::set_point(const synfig::Point &x)
{
    if (is_aspect_locked()) {
        point_ = aspect_point_ * (x * aspect_point_);
    } else {
        point_ = x;
    }

    if (shared_point_) {
        *shared_point_ = point_;
    }

    if (shared_angle_) {
        *shared_angle_ = point_.angle();
    }

    if (shared_mag_) {
        *shared_mag_ = point_.mag();
    }
}

// Returns the location of the duck
synfig::Point
Duck::get_point()const
{
    synfig::Point p;

    if (!shared_point_ && !shared_angle_ && !shared_mag_) {
        p = point_;
    } else if (shared_point_) {
        p = *shared_point_;
    } else
        p = synfig::Point(
                shared_mag_ ? *shared_mag_ : point_.mag(),
                shared_angle_ ? *shared_angle_ : point_.angle());

    if (is_aspect_locked()) {
        p = aspect_point_ * (p * aspect_point_);
    }

    return p;
}

synfig::Point
Duck::get_trans_point()const
{
    return transform_stack_.perform(get_sub_trans_point());
}

void
Duck::set_trans_point(const synfig::Point &x)
{
    set_sub_trans_point(transform_stack_.unperform(x));
}

void
Duck::set_trans_point(const synfig::Point &x, const synfig::Time &time)
{
    set_sub_trans_point(transform_stack_.unperform(x), time);
}

// Retrieves the origin location
synfig::Point
Duck::get_trans_origin()const
{
    return transform_stack_.perform(get_sub_trans_origin());
}

synfig::Point
Duck::get_sub_trans_point_without_offset(const synfig::Point &x)const
{
    Point p(x * get_scalar());
    return get_axis_x() * p[0]
           + get_axis_y() * p[1];
}

synfig::Point
Duck::get_sub_trans_point(const synfig::Point &x)const
{
    Point p(x * get_scalar());
    return get_axis_x() * p[0]
           + get_axis_y() * p[1]
           + get_sub_trans_origin();
}

synfig::Point
Duck::get_sub_trans_point()const
{
    return get_sub_trans_point(get_point());
}

synfig::Point
Duck::get_sub_trans_point_without_offset()const
{
    return get_sub_trans_point_without_offset(get_point());
}

void
Duck::set_sub_trans_point(const synfig::Point &x)
{
    Matrix m(get_axis_x(), get_axis_y(), get_sub_trans_origin());
    m.invert();

    Angle old_angle = get_point().angle();
    set_point(m.get_transformed(x) / get_scalar());
    Angle change = get_point().angle() - old_angle;

    while (change < Angle::deg(-180)) {
        change += Angle::deg(360);
    }

    while (change > Angle::deg(180)) {
        change -= Angle::deg(360);
    }

    rotations_ += change;
}

void
Duck::set_sub_trans_point(const synfig::Point &x, const synfig::Time &time)
{
    set_sub_trans_point(x);

    if (get_type() == Duck::TYPE_VERTEX
            || get_type() == Duck::TYPE_POSITION
            || get_type() == Duck::TYPE_WIDTHPOINT_POSITION) {
        ValueNode_BLineCalcVertex::Handle bline_vertex;
        ValueNode_Composite::Handle composite;

        if ((bline_vertex = ValueNode_BLineCalcVertex::Handle::cast_dynamic(get_value_desc().get_value_node())) ||
                ((composite = ValueNode_Composite::Handle::cast_dynamic(get_value_desc().get_value_node())) &&
                 composite->get_type() == type_bline_point &&
                 (bline_vertex = ValueNode_BLineCalcVertex::Handle::cast_dynamic(composite->get_link("point"))))) {
            synfig::Point closest_point = get_point();
            synfig::Real radius = 0.0;
            ValueNode_BLine::Handle bline = ValueNode_BLine::Handle::cast_dynamic(bline_vertex->get_link("bline"));
            synfig::find_closest_point(
                (*bline)(time),
                get_point(),
                radius,
                bline->get_loop(),
                &closest_point);
            set_point(closest_point);
        }

        ValueNode_Composite::Handle wpoint_composite;
        ValueNode_WPList::Handle wplist;
        wpoint_composite = ValueNode_Composite::Handle::cast_dynamic(get_value_desc().get_value_node());

        if (wpoint_composite && wpoint_composite->get_type() == type_width_point)
            if (get_value_desc().parent_is_value_node()) {
                wplist = ValueNode_WPList::Handle::cast_dynamic(get_value_desc().get_parent_value_node());

                if (wplist) {
                    ValueNode_BLine::Handle bline(ValueNode_BLine::Handle::cast_dynamic(wplist->get_bline()));
                    synfig::Point closest_point = get_point();
                    synfig::Real radius = 0.0;
                    synfig::find_closest_point(
                        (*bline)(time),
                        get_point(),
                        radius,
                        bline->get_loop(),
                        &closest_point);
                    set_point(closest_point);
                }
            }
    }
}

synfig::Point
Duck::get_sub_trans_point(const Handle &duck, const Point &def, bool translate)const
{
    // The origin needs to have the same transform stack as this duck
    return !duck ? def
           : translate ? transform_stack_.unperform(duck->get_trans_point())
           : transform_stack_.unperform(duck->get_trans_point())
           - transform_stack_.unperform(duck->get_trans_origin());
}

synfig::Point
Duck::get_sub_trans_origin()const
{
    return get_sub_trans_point(origin_duck_, origin_);
}

#ifdef _DEBUG
synfig::String
Duck::type_name(Type id)
{
    String ret;

    if (id & TYPE_POSITION) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "position";
    }

    if (id & TYPE_TANGENT) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "tangent" ;
    }

    if (id & TYPE_RADIUS) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "radius"  ;
    }

    if (id & TYPE_WIDTH) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "width"   ;
    }

    if (id & TYPE_ANGLE) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "angle"   ;
    }

    if (id & TYPE_VERTEX) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "vertex"  ;
    }

    if (id & TYPE_WIDTHPOINT_POSITION) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "widthpoint position"  ;
    }

    if (id & TYPE_SCALE) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "scale"   ;
    }

    if (id & TYPE_SCALE_X) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "scale-x" ;
    }

    if (id & TYPE_SCALE_Y) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "scale-y" ;
    }

    if (id & TYPE_SKEW) {
        if (!ret.empty()) {
            ret += ", ";
        }

        ret += "skew" ;
    }

    if (ret.empty()) {
        ret = "none";
    }

    return ret;
}
#endif	// _DEBUG